/*
 * ScreenManager.cpp
 *
 *  Created on: 10/09/2011
 *      Author: Aeradon
 */

#include "ScreenManager.h"

#include "triangulation.hpp"
#include "NavimeshRenderer.h"
#include "edge.hpp"

#include <sstream>
#include <cmath>
#include <ctime>
#include <cstdio>
#include <cstdlib>
#include <cstring>

ScreenManager* ScreenManager::instance = NULL;

ScreenManager::ScreenManager():
		zoom(1.0),
		cameraX(-6400.0),
		cameraY(6400.0),
		resolutionW(SCR_DEF_WIDTH),
		resolutionH(SCR_DEF_HEIGHT),
		window(nullptr)
{
	/// Limpeza nos buffers
	clearBuffers();

	/// initialize SDL
	if(SDL_Init(SDL_INIT_VIDEO) < 0 )
	{
		fprintf( stderr, "Video initialization failed: %s\n", SDL_GetError() );
		exit( 1 );
	}

	viewPortX1 = -resolutionW/2;
	viewPortY1 = resolutionH/2;
	viewPortX2 = resolutionW/2;
	viewPortY2 = -resolutionH/2;

	/// Create the window
	window = SDL_CreateWindow("DeltaPath Renderer",
            SDL_WINDOWPOS_CENTERED,
            SDL_WINDOWPOS_CENTERED,
            resolutionW, resolutionH,
            SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);

	/// Verify there is a window
	if(!window)
	{
		fprintf( stderr,  "Unable to create Window: %s\n", SDL_GetError( ) );
		exit( 1 );
	}

	glContext = SDL_GL_CreateContext(window);
	/// initialize OpenGL
	if (!(InitGL()) )
	{
    	fprintf( stderr,  "Unable to Initialize OpenGL: %s\n", SDL_GetError( ) );
    	exit( 1 );
	}
}

/* general OpenGL initialization function */
int ScreenManager::InitGL()
{
	glewInit();

	/// Sets up OpenGL double buffering
	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

	// Enable Texture Mapping
	//glEnable(GL_TEXTURE_2D);
	//glEnable(GL_TEXTURE_2D_ARRAY_EXT);
	//glEnable(GL_TEXTURE_2D_ARRAY);
	//glEnable(GL_TEXTURE_3D);

	// shading mathod: GL_SMOOTH or GL_FLAT
	//glShadeModel( GL_FLAT );

	// background color
	glClearColor( 0.0, 0.0, 0.0, 0.0);

	/* Depth buffer setup */
	glClearDepth( 1000.0 );
	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LEQUAL);
	//glDisable(GL_DEPTH_TEST);

	// Perspective Calculations: GL_NICEST or GL_FASTEST  */
    glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST );
	//glHint(GL_LINE_SMOOTH_HINT, GL_FASTEST);
	//glHint(GL_POLYGON_SMOOTH_HINT, GL_FASTEST);

    // Enable Blending
    //glBlendFunc(GL_SRC_ALPHA, GL_ONE);
    //glEnable(GL_BLEND);
    glDisable(GL_BLEND);

	//TODO Enable Face Culling
	//glEnable(GL_CULL_FACE);
    //glCullFace(GL_BACK);
    //glFrontFace(GL_CCW);
    glDisable(GL_CULL_FACE);

	glClearStencil(0);                          // clear stencil buffer
	glClearDepth(1000.0f);                         // 0 is near, 1 is far
	//glDepthFunc(GL_LEQUAL);

	// Lighting initialization
	// Disable Lightning
	glDisable(GL_LIGHTING);
    //initLights();

	// SetUp projection
	glMatrixMode (GL_PROJECTION);
	glLoadIdentity ();
	//glViewport(0, 0, viewPortX2, viewPortY2);
	// 2D Projection
	glOrtho(viewPortX1, viewPortX2, viewPortY2, viewPortY1, -1000, 1000);
	// 3D Projection
	//gluPerspective(50.0, ((GLfloat)SCR_DEF_WIDTH / (GLfloat)SCR_DEF_HEIGHT), 0.1f, 100.0f );

	UpdateZoom(0.026f);

    return(true);
}

ScreenManager* ScreenManager::GetInstance()
{
    if (!instance)
    {
    	instance = new ScreenManager();
    }
    return instance;
}

ScreenManager::~ScreenManager()
{
    /* clean up the window */
    SDL_Quit();
}

/* set camera position and lookat direction */
void ScreenManager::updateCamera()
{
	// Usar Push matrix e PopPatrix para fazer a renderiza��o da interface
	// A renderizacao deve ser dividida em 2 partes, sendo a 1a afetada pela camera, e a 2a nao

	// SetUp Camera
	glMatrixMode( GL_MODELVIEW );
	glLoadIdentity();
	// 3D Camera
	//gluLookAt(cameraX, cameraY, zoom,cameraX, cameraY, 0, 0, 1, 0);
	// 2D Camera
	glOrtho(viewPortX1, viewPortX2, viewPortY2, viewPortY1, -1000, 1000);
	glTranslatef(cameraX, cameraY,0);
}

void ScreenManager::UpdateZoom(float percent)
{
	zoom *= percent;
	viewPortX1 *= percent;
	viewPortX2 *= percent;
	viewPortY1 *= percent;
	viewPortY2 *= percent;
}

void ScreenManager::resizeEvent(int w, int h)
{
	float changeW = (float)w/(float)resolutionW;
	float changeH = (float)h/(float)resolutionH;
	float change = changeW;
	if (changeH > changeW) change = changeH;

	viewPortX1 *= change;
	viewPortY1 *= change;
	viewPortX2 *= change;
	viewPortY2 *= change;

	resolutionW = w;
	resolutionH = h;

	int maxwh = (w > h) ? w : h;

	glViewport(0, 0, maxwh, maxwh);
	updateCamera();
}


/* Here goes our drawing code */
int ScreenManager::RenderTriangles()
{
	updateCamera();

	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_COLOR_ARRAY);

	glVertexPointer(3, GL_FLOAT, 0, objectVertices);
	glColorPointer(4,GL_FLOAT,0,objectColors);

	glDrawArrays(GL_TRIANGLES,0,count_vertex);

	glDisableClientState(GL_COLOR_ARRAY);
	glDisableClientState(GL_VERTEX_ARRAY);

	//RENDERIZACAO END

	return( true );
}

/* Here goes our drawing code */
int ScreenManager::RenderLines()
{
	updateCamera();

	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_COLOR_ARRAY);

	glVertexPointer(3, GL_FLOAT, 0, objectVertices);
	glColorPointer(4,GL_FLOAT,0,objectColors);

	glDrawArrays(GL_LINES,0,count_vertex);

	glDisableClientState(GL_COLOR_ARRAY);
	glDisableClientState(GL_VERTEX_ARRAY);

	//  RENDERIZACAO END

	return( true );
}

/*
 *  check FBO completeness
 */
bool ScreenManager::checkFramebufferStatus()
{
    // check FBO status
    GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
    switch(status)
    {
    case GL_FRAMEBUFFER_COMPLETE_EXT:
        printf("Framebuffer complete.\n");
        return true;

    case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
    	printf("[ERROR] Framebuffer incomplete: Attachment is NOT complete.\n");
        return false;

    case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
    	printf("[ERROR] Framebuffer incomplete: No image is attached to FBO.\n");
        return false;

    case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
    	printf("[ERROR] Framebuffer incomplete: Attached images have different dimensions.\n");
        return false;

    case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
    	printf("[ERROR] Framebuffer incomplete: Color attached images have different internal formats.\n");
        return false;

    case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
    	printf("[ERROR] Framebuffer incomplete: Draw buffer.\n");
        return false;

    case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
    	printf("[ERROR] Framebuffer incomplete: Read buffer.\n");
        return false;

    case GL_FRAMEBUFFER_UNSUPPORTED_EXT:
    	printf("[ERROR] Unsupported by FBO implementation.\n");
        return false;

    default:
    	printf("[ERROR] Unknow error.\n");
        return false;
    }
}


/*
 * print out the FBO info
 */
void ScreenManager::printFramebufferInfo()
{
    printf("\n***** FBO STATUS *****\n");

    // print max # of colorbuffers supported by FBO
    int colorBufferCount = 0;
    glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS_EXT, &colorBufferCount);
    printf("Max Number of Color Buffer Attachment Points: %d,\n",colorBufferCount);

    int objectType;
    int objectId;

    // print info of the colorbuffer attachable image
    for(int i = 0; i < colorBufferCount; ++i)
    {
        glGetFramebufferAttachmentParameterivEXT(GL_FRAMEBUFFER_EXT,
                                                 GL_COLOR_ATTACHMENT0_EXT+i,
                                                 GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE_EXT,
                                                 &objectType);
        if(objectType != GL_NONE)
        {
            glGetFramebufferAttachmentParameterivEXT(GL_FRAMEBUFFER_EXT,
                                                     GL_COLOR_ATTACHMENT0_EXT+i,
                                                     GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME_EXT,
                                                     &objectId);

            std::string formatName;

            printf("Color Attachment %d: ",i);
            if(objectType == GL_TEXTURE)
            	printf("GL_TEXTURE, %s \n",getTextureParameters(objectId).c_str());
            else if(objectType == GL_RENDERBUFFER_EXT)
            	printf("GL_RENDERBUFFER_EXT, %s\n",getRenderbufferParameters(objectId).c_str());
        }
    }

    // print info of the depthbuffer attachable image
    glGetFramebufferAttachmentParameterivEXT(GL_FRAMEBUFFER_EXT,
                                             GL_DEPTH_ATTACHMENT_EXT,
                                             GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE_EXT,
                                             &objectType);
    if(objectType != GL_NONE)
    {
        glGetFramebufferAttachmentParameterivEXT(GL_FRAMEBUFFER_EXT,
                                                 GL_DEPTH_ATTACHMENT_EXT,
                                                 GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME_EXT,
                                                 &objectId);

        printf("Depth Attachment: ");
        switch(objectType)
        {
        case GL_TEXTURE:
        	printf("GL_TEXTURE, %s\n",getTextureParameters(objectId).c_str());
            break;
        case GL_RENDERBUFFER_EXT:
        	printf("GL_RENDERBUFFER_EXT, %s\n",getRenderbufferParameters(objectId).c_str());
            break;
        }
    }

    // print info of the stencilbuffer attachable image
    glGetFramebufferAttachmentParameterivEXT(GL_FRAMEBUFFER_EXT,
                                             GL_STENCIL_ATTACHMENT_EXT,
                                             GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE_EXT,
                                             &objectType);
    if(objectType != GL_NONE)
    {
        glGetFramebufferAttachmentParameterivEXT(GL_FRAMEBUFFER_EXT,
                                                 GL_STENCIL_ATTACHMENT_EXT,
                                                 GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME_EXT,
                                                 &objectId);

        printf("Stencil Attachment: ");
        switch(objectType)
        {
        case GL_TEXTURE:
            printf("GL_TEXTURE, %s\n", getTextureParameters(objectId).c_str());
            break;
        case GL_RENDERBUFFER_EXT:
        	printf("GL_RENDERBUFFER_EXT, %s\n", getRenderbufferParameters(objectId).c_str());
            break;
        }
    }

    printf("\n");
}

void ScreenManager::addVertex(float x, float y, float z, GLColor color)
{
	objectVertices[count_coord++] = x *100.0f;
	objectColors[count_color++] = color.r;

	objectVertices[count_coord++] = -y*100.0f;
	objectColors[count_color++] = color.g;

	objectVertices[count_coord++] = z;
	objectColors[count_color++] = color.b;

	objectColors[count_color++] = 0.5f;

	//objectIndices[count_vertex] = count_vertex;
	count_vertex++;
}

void ScreenManager::testRenderer()
{
	clearBuffers();

	objectVertices[count_coord++] = 0.0f;
	objectColors[count_color++]   = 1.0f;
	objectVertices[count_coord++] = 0.0f;
	objectColors[count_color++]   = 1.0f;
	objectVertices[count_coord++] = 10.0f;
	objectColors[count_color++]   = 1.0f;
	objectColors[count_color++]   = 1.0f;

	objectVertices[count_coord++] = 1280.0f;
	objectColors[count_color++]   = 1.0f;
	objectVertices[count_coord++] = 0.0f;
	objectColors[count_color++]   = 1.0f;
	objectVertices[count_coord++] = 10.0f;
	objectColors[count_color++]   = 1.0f;
	objectColors[count_color++]   = 1.0f;

	objectVertices[count_coord++] = 1280.0f;
	objectColors[count_color++]   = 1.0f;
	objectVertices[count_coord++] = 1280.0f;
	objectColors[count_color++]   = 1.0f;
	objectVertices[count_coord++] = 10.0f;
	objectColors[count_color++]   = 1.0f;
	objectColors[count_color++]   = 1.0f;

	count_vertex = 3;

}

void ScreenManager::saveScreenShot()
{
	time_t rawtime = time(NULL);
	tm *  timeinfo = localtime ( &rawtime );
	char tempo[50];
	tempo[0] = '\0';

	sprintf(tempo,"PFss%d%02d%02d_%02d%02d%02d.bmp",
			(timeinfo->tm_year + 1900),(timeinfo->tm_mon + 1),timeinfo->tm_mday,timeinfo->tm_hour,timeinfo->tm_min,timeinfo->tm_sec);

	SDL_Surface * screenShot = SDL_CreateRGBSurface(SDL_SWSURFACE, SCR_DEF_WIDTH, SCR_DEF_HEIGHT, 24,
			0x000000FF, 0x0000FF00, 0x00FF0000, 0);

	glReadPixels(0, 0, SCR_DEF_WIDTH, SCR_DEF_HEIGHT, GL_RGB, GL_UNSIGNED_BYTE, screenShot->pixels);

	SDL_SaveBMP(screenShot,tempo);
	SDL_FreeSurface(screenShot);
}

void ScreenManager::clearBuffers()
{
	count_vertex = 0;
	count_coord = 0;
	count_color = 0;

	memset(objectVertices, 0, sizeof(GLfloat)*(MAX_OBJECTS *9));
	memset(objectColors,   0, sizeof(GLfloat)*(MAX_OBJECTS *12));
}

void ScreenManager::Present()
{
	SDL_GL_SwapWindow(window);
	ClearScreen();
}

void ScreenManager::ClearScreen()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}


/*
 * return texture parameters as string using glGetTexLevelParameteriv()
 */
std::string ScreenManager::getTextureParameters(GLuint id)
{
    if(glIsTexture(id) == GL_FALSE)
        return "Not texture object";

    int width, height, format;
    std::string formatName;
    glBindTexture(GL_TEXTURE_2D, id);
    glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width);            // get texture width
    glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height);          // get texture height
    glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &format); // get texture internal format
    glBindTexture(GL_TEXTURE_2D, 0);

    formatName = convertInternalFormatToString(format);

    std::stringstream ss;
    ss << width << "x" << height << ", " << formatName;
    return ss.str();
}

/*
 * return renderbuffer parameters as string using glGetRenderbufferParameterivEXT
 */
std::string ScreenManager::getRenderbufferParameters(GLuint id)
{
    if(glIsRenderbufferEXT(id) == GL_FALSE)
        return "Not Renderbuffer object";

    int width, height, format;
    std::string formatName;
    glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, id);
    glGetRenderbufferParameterivEXT(GL_RENDERBUFFER_EXT, GL_RENDERBUFFER_WIDTH_EXT, &width);    // get renderbuffer width
    glGetRenderbufferParameterivEXT(GL_RENDERBUFFER_EXT, GL_RENDERBUFFER_HEIGHT_EXT, &height);  // get renderbuffer height
    glGetRenderbufferParameterivEXT(GL_RENDERBUFFER_EXT, GL_RENDERBUFFER_INTERNAL_FORMAT_EXT, &format); // get renderbuffer internal format
    glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);

    formatName = convertInternalFormatToString(format);

    std::stringstream ss;
    ss << width << "x" << height << ", " << formatName;
    return ss.str();
}

/*
 * convert OpenGL internal format enum to string
 */
std::string ScreenManager::convertInternalFormatToString(GLenum format)
{
    std::string formatName;

    switch(format)
    {
    case GL_STENCIL_INDEX:
        formatName = "GL_STENCIL_INDEX";
        break;
    case GL_DEPTH_COMPONENT:
        formatName = "GL_DEPTH_COMPONENT";
        break;
    case GL_ALPHA:
        formatName = "GL_ALPHA";
        break;
    case GL_RGB:
        formatName = "GL_RGB";
        break;
    case GL_RGBA:
        formatName = "GL_RGBA";
        break;
    case GL_LUMINANCE:
        formatName = "GL_LUMINANCE";
        break;
    case GL_LUMINANCE_ALPHA:
        formatName = "GL_LUMINANCE_ALPHA";
        break;
    case GL_ALPHA4:
        formatName = "GL_ALPHA4";
        break;
    case GL_ALPHA8:
        formatName = "GL_ALPHA8";
        break;
    case GL_ALPHA12:
        formatName = "GL_ALPHA12";
        break;
    case GL_ALPHA16:
        formatName = "GL_ALPHA16";
        break;
    case GL_LUMINANCE4:
        formatName = "GL_LUMINANCE4";
        break;
    case GL_LUMINANCE8:
        formatName = "GL_LUMINANCE8";
        break;
    case GL_LUMINANCE12:
        formatName = "GL_LUMINANCE12";
        break;
    case GL_LUMINANCE16:
        formatName = "GL_LUMINANCE16";
        break;
    case GL_LUMINANCE4_ALPHA4:
        formatName = "GL_LUMINANCE4_ALPHA4";
        break;
    case GL_LUMINANCE6_ALPHA2:
        formatName = "GL_LUMINANCE6_ALPHA2";
        break;
    case GL_LUMINANCE8_ALPHA8:
        formatName = "GL_LUMINANCE8_ALPHA8";
        break;
    case GL_LUMINANCE12_ALPHA4:
        formatName = "GL_LUMINANCE12_ALPHA4";
        break;
    case GL_LUMINANCE12_ALPHA12:
        formatName = "GL_LUMINANCE12_ALPHA12";
        break;
    case GL_LUMINANCE16_ALPHA16:
        formatName = "GL_LUMINANCE16_ALPHA16";
        break;
    case GL_INTENSITY:
        formatName = "GL_INTENSITY";
        break;
    case GL_INTENSITY4:
        formatName = "GL_INTENSITY4";
        break;
    case GL_INTENSITY8:
        formatName = "GL_INTENSITY8";
        break;
    case GL_INTENSITY12:
        formatName = "GL_INTENSITY12";
        break;
    case GL_INTENSITY16:
        formatName = "GL_INTENSITY16";
        break;
    case GL_R3_G3_B2:
        formatName = "GL_R3_G3_B2";
        break;
    case GL_RGB4:
        formatName = "GL_RGB4";
        break;
    case GL_RGB5:
        formatName = "GL_RGB4";
        break;
    case GL_RGB8:
        formatName = "GL_RGB8";
        break;
    case GL_RGB10:
        formatName = "GL_RGB10";
        break;
    case GL_RGB12:
        formatName = "GL_RGB12";
        break;
    case GL_RGB16:
        formatName = "GL_RGB16";
        break;
    case GL_RGBA2:
        formatName = "GL_RGBA2";
        break;
    case GL_RGBA4:
        formatName = "GL_RGBA4";
        break;
    case GL_RGB5_A1:
        formatName = "GL_RGB5_A1";
        break;
    case GL_RGBA8:
        formatName = "GL_RGBA8";
        break;
    case GL_RGB10_A2:
        formatName = "GL_RGB10_A2";
        break;
    case GL_RGBA12:
        formatName = "GL_RGBA12";
        break;
    case GL_RGBA16:
        formatName = "GL_RGBA16";
        break;
    default:
        formatName = "Unknown Format";
    break;
    }

    return formatName;
}

void ScreenManager::RenderTriangulation()
{
	if (!renderTriangulation)
		return;

	clearBuffers();

	std::vector<Face*>::iterator itr_face = triangulation->getVFace().begin();
	for (++itr_face;
		 itr_face != triangulation->getVFace().end();
		 ++itr_face)
	{
		for (int i = 0; i< (*itr_face)->getEdgeCount(); ++i)
		{
			addVertex(
					(*itr_face)->getEdge(i)->vtx()->x,
					(*itr_face)->getEdge(i)->vtx()->y,
					0.0f,
					((NavinodeRenderable*)(*itr_face)->node)->Color());
		}
	}

	RenderTriangles();
}

void ScreenManager::RenderConstraints(bool only)
{
	if (!renderTriangulation)
		return;

	clearBuffers();
	GLColor color;

	for (std::vector<Edge*>::iterator itr_edge = triangulation->getVEdge().begin();
			itr_edge != triangulation->getVEdge().end();
			++itr_edge)
	{
		// Build colors according to constraints
		if ((*itr_edge)->isConstrained)
		{
			color = {1.0f, 0.0f, 0.0f};
		}
		else // the edge is not constrained
		{
			color = {0.0f, 1.0f, 0.0f};
		}

		if (only)
		{
			if ((*itr_edge)->isConstrained)
			{
				addVertex(
						(*itr_edge)->v1->x,
						(*itr_edge)->v1->y,
						-10.0f,
						color);
				addVertex(
						(*itr_edge)->v2->x,
						(*itr_edge)->v2->y,
						-10.0f,
						color);
			}
		}
		else
		{
			addVertex(
					(*itr_edge)->v1->x,
					(*itr_edge)->v1->y,
					-5.0f,
					color);
			addVertex(
					(*itr_edge)->v2->x,
					(*itr_edge)->v2->y,
					-5.0f,
					color);
		}
	}

	RenderLines();
}

void ScreenManager::ShowTriangulation()
{
	ClearScreen();
	RenderTriangulation();
	Present();
}

void ScreenManager::ShowConstraints(bool only)
{
	ClearScreen();
	RenderConstraints(only);
	Present();
}

// static wrapper-function to be able to callback the member function Callback()
void ScreenManagerCallBack(void* obj)
{
    // explicitly cast to a pointer to TClassA
    ScreenManager* mySelf = (ScreenManager*) obj;

    // call member
    mySelf->ShowTriangulation();
}
